// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// The serialization format is as follows:
//   16-bit integer describing the following LogMetadata proto size in bytes.
//   The LogMetadata proto.
//   32-bit integer describing number of frame events.
//   (The following repeated for number of frame events):
//     16-bit integer describing the following AggregatedFrameEvent proto size
//         in bytes.
//     The AggregatedFrameEvent proto.
//   32-bit integer describing number of packet events.
//   (The following repeated for number of packet events):
//     16-bit integer describing the following AggregatedPacketEvent proto
//         size in bytes.
//     The AggregatedPacketEvent proto.

#include "media/cast/logging/log_serializer.h"

#include <stdint.h>

#include <memory>

#include "base/big_endian.h"
#include "base/logging.h"
#include "third_party/zlib/zlib.h"

namespace media {
namespace cast {

    namespace {

        using media::cast::proto::AggregatedFrameEvent;
        using media::cast::proto::AggregatedPacketEvent;
        using media::cast::proto::LogMetadata;

        // Use 30MB of temp buffer to hold uncompressed data if |compress| is true.
        const int kMaxUncompressedBytes = 30 * 1000 * 1000;

        // The maximum allowed size per serialized proto.
        const int kMaxSerializedProtoBytes = (1 << 16) - 1;
        bool DoSerializeEvents(const LogMetadata& metadata,
            const FrameEventList& frame_events,
            const PacketEventList& packet_events,
            const int max_output_bytes,
            char* output,
            int* output_bytes)
        {
            base::BigEndianWriter writer(output, max_output_bytes);

            int proto_size = metadata.ByteSize();
            DCHECK(proto_size <= kMaxSerializedProtoBytes);
            if (!writer.WriteU16(static_cast<uint16_t>(proto_size)))
                return false;
            if (!metadata.SerializeToArray(writer.ptr(), writer.remaining()))
                return false;
            if (!writer.Skip(proto_size))
                return false;

            RtpTimeTicks prev_rtp_timestamp;
            for (media::cast::FrameEventList::const_iterator it = frame_events.begin();
                 it != frame_events.end();
                 ++it) {
                media::cast::proto::AggregatedFrameEvent frame_event(**it);

                // Adjust relative RTP timestamp so that it is relative to previous frame,
                // rather than relative to first RTP timestamp.
                // This is done to improve encoding size.
                const RtpTimeTicks rtp_timestamp = prev_rtp_timestamp.Expand(frame_event.relative_rtp_timestamp());
                frame_event.set_relative_rtp_timestamp(
                    (rtp_timestamp - prev_rtp_timestamp).lower_32_bits());
                prev_rtp_timestamp = rtp_timestamp;

                proto_size = frame_event.ByteSize();
                DCHECK(proto_size <= kMaxSerializedProtoBytes);

                // Write size of the proto, then write the proto.
                if (!writer.WriteU16(static_cast<uint16_t>(proto_size)))
                    return false;
                if (!frame_event.SerializeToArray(writer.ptr(), writer.remaining()))
                    return false;
                if (!writer.Skip(proto_size))
                    return false;
            }

            // Write packet events.
            prev_rtp_timestamp = RtpTimeTicks();
            for (media::cast::PacketEventList::const_iterator it = packet_events.begin();
                 it != packet_events.end();
                 ++it) {
                media::cast::proto::AggregatedPacketEvent packet_event(**it);

                const RtpTimeTicks rtp_timestamp = prev_rtp_timestamp.Expand(packet_event.relative_rtp_timestamp());
                packet_event.set_relative_rtp_timestamp(
                    (rtp_timestamp - prev_rtp_timestamp).lower_32_bits());
                prev_rtp_timestamp = rtp_timestamp;

                proto_size = packet_event.ByteSize();
                DCHECK(proto_size <= kMaxSerializedProtoBytes);

                // Write size of the proto, then write the proto.
                if (!writer.WriteU16(static_cast<uint16_t>(proto_size)))
                    return false;
                if (!packet_event.SerializeToArray(writer.ptr(), writer.remaining()))
                    return false;
                if (!writer.Skip(proto_size))
                    return false;
            }

            *output_bytes = max_output_bytes - writer.remaining();
            return true;
        }

        bool Compress(char* uncompressed_buffer,
            int uncompressed_bytes,
            int max_output_bytes,
            char* output,
            int* output_bytes)
        {
            z_stream stream = { 0 };
            int result = deflateInit2(&stream,
                Z_DEFAULT_COMPRESSION,
                Z_DEFLATED,
                // 16 is added to produce a gzip header + trailer.
                MAX_WBITS + 16,
                8, // memLevel = 8 is default.
                Z_DEFAULT_STRATEGY);
            DCHECK_EQ(Z_OK, result);

            stream.next_in = reinterpret_cast<uint8_t*>(uncompressed_buffer);
            stream.avail_in = uncompressed_bytes;
            stream.next_out = reinterpret_cast<uint8_t*>(output);
            stream.avail_out = max_output_bytes;

            // Do a one-shot compression. This will return Z_STREAM_END only if |output|
            // is large enough to hold all compressed data.
            result = deflate(&stream, Z_FINISH);
            bool success = (result == Z_STREAM_END);

            if (!success)
                DVLOG(2) << "deflate() failed. Result: " << result;

            result = deflateEnd(&stream);
            DCHECK(result == Z_OK || result == Z_DATA_ERROR);

            if (success)
                *output_bytes = max_output_bytes - stream.avail_out;

            return success;
        }

    } // namespace

    bool SerializeEvents(const LogMetadata& log_metadata,
        const FrameEventList& frame_events,
        const PacketEventList& packet_events,
        bool compress,
        int max_output_bytes,
        char* output,
        int* output_bytes)
    {
        DCHECK_GT(max_output_bytes, 0);
        DCHECK(output);
        DCHECK(output_bytes);

        if (compress) {
            // Allocate a reasonably large temp buffer to hold uncompressed data.
            std::unique_ptr<char[]> uncompressed_buffer(
                new char[kMaxUncompressedBytes]);
            int uncompressed_bytes;
            bool success = DoSerializeEvents(log_metadata,
                frame_events,
                packet_events,
                kMaxUncompressedBytes,
                uncompressed_buffer.get(),
                &uncompressed_bytes);
            if (!success)
                return false;
            return Compress(uncompressed_buffer.get(),
                uncompressed_bytes,
                max_output_bytes,
                output,
                output_bytes);
        } else {
            return DoSerializeEvents(log_metadata,
                frame_events,
                packet_events,
                max_output_bytes,
                output,
                output_bytes);
        }
    }

} // namespace cast
} // namespace media
